#include <assert.h>
#include <errno.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <err.h>
#include <sysexits.h>

#include <zahl.h>

#include "dc.h"
#include "util.h"

#ifndef nitems
#define nitems(a) (sizeof(a) / sizeof((a)[0]))
#endif

#define zprint(z)				\
	do {					\
		char *s = zstr((z), NULL, 0);	\
		(void)fprintf(stderr, "%s", s);	\
	} while (0)

void
dcinit(struct dc *d)
{

	assert(d != NULL);
	(void)memset(d, 0, sizeof(struct dc));
	d->stack.size = sizeof(struct dcent);
	d->rstack.size = sizeof(struct dcret);
	d->str.size = 1;
	zinit(d->k);
	zsetu(d->k, 1);
	d->i = 10;
	d->o = 10;
}

void
dcfree(struct dc *d)
{
	FILE *fp;
	size_t i;

	assert(d != NULL);
	for (i = 0; i < nitems(d->reg); i++)
		dcent_free(d->reg + i);
	dcclr(d);
	while ((fp = dcret_pop(&d->rstack)) != NULL)
		fclose(fp);
	if (d->stack.data != NULL)
		free(d->stack.data);
	if (d->rstack.data != NULL)
		free(d->rstack.data);
	if (d->str.data != NULL)
		free(d->str.data);
	zfree(d->k);
}

int
dcent_dup(struct dcent *dst, struct dcent *src)
{

	assert(dst != NULL && src != NULL);
	switch (dst->type = src->type) {
	case DCNUM:
		zinit(dst->u.n.v);
		zset(dst->u.n.v, src->u.n.v);
		zinit(dst->u.n.k);
		zset(dst->u.n.k, src->u.n.k);
		break;
	case DCSTR:
		dst->u.s.str = memdup(src->u.s.str, src->u.s.len);
		dst->u.s.len = src->u.s.len;
		break;
	default:
		return (-1);
	}
	return (0);
}

void
dcent_free(struct dcent *e)
{

	if (e == NULL)
		return;
	switch (e->type) {
	case DCNUM:
		zfree(e->u.n.v);
		zfree(e->u.n.k);
		break;
	case DCSTR:
		free(e->u.s.str);
		break;
	case DCSTACK:
		if (e->u.d.data != NULL)
			free(e->u.d.data);
		break;
	}
	e->type = DCUNDEF;
}

int
dcent_pop(struct data *d, int t, struct dcent *o, size_t n)
{
	struct dcent *e;
	size_t i;

	assert(d != NULL && o != NULL);
	if (n > d->nmemb)
		return (-1);
	e = (struct dcent *)d->data + d->nmemb - n;
	if (t != 0)
		for (i = 0; i < n; i++)
			if (e[i].type != t)
				return (-1);
	for (i = n; i > 0; )
		(void)memcpy(o++, e + --i, sizeof(struct dcent));
	d->nmemb -= n;
	return (0);
}

int
dcent_push(struct data *d, struct dcent *e, size_t n)
{
	struct dcent *p;
	size_t i;

	assert(d != NULL && e != NULL);
	for (i = 0; i < n; i++) {
		if ((p = dallot(d, 4)) == NULL)
			return (-1);
		(void)memcpy(p, e + i, sizeof(struct dcent));
	}
	return (0);
}

int
dcent_print(struct dcent *e, unsigned ou, FILE *fp)
{
	z_t o;
	z_t v;
	z_t p;
	z_t t;
	size_t i;
	int j;

	assert(e != NULL && ou <= 16 && fp != NULL);
	switch (e->type) {
	case DCNUM:
		zinit(o);
		zinit(v);
		zinit(p);
		zinit(t);
		zsetu(o, ou);
		zset(v, e->u.n.v);
		zset(p, e->u.n.k);
		if (zsignum(v) == -1) {
			if (fputc('-', fp) == EOF)
				goto neof;
			zabs(v, v);
		}
		for (i = 0; zcmp(p, v) != 1; i++)
			zmul(p, p, o);
		if (i > 0) {
			zdiv(p, p, o);
			for (; i > 0; i--) {
				zdivmod(t, v, v, p);
				zdiv(p, p, o);
				for (j = 0; j < ou &&
				    zcmpu(t, j) != 0; j++)
					/* do nothing */;
				if (j == ou)
					abort();
				if (j < 10) {
					if (fputc('0' + j, fp) == EOF)
						goto neof;
				} else {
					if (fputc('A' + j, fp) == EOF)
						goto neof;
				}
			}
		} else {
			if (fputc('0', fp) == EOF)
				goto neof;
			zdiv(p, p, o);
		}
		if (zsignum(v) > 0) {
			if (fputc('.', fp) == EOF)
				goto neof;
			do {
				zdivmod(t, v, v, p);
				zdiv(p, p, o);
				for (j = 0; j < ou && zcmpu(t, j) != 0;
				    j++)
					/* do nothing */;
				assert(j < ou);
				if (j < 10) {
					if (fputc('0' + j, fp) == EOF)
						goto neof;
				} else {
					if (fputc('A' + j, fp) == EOF)
						goto neof;
				}
			} while (zsignum(v) > 0);
		}
		if (fputc('\n', fp) == EOF)
			goto neof;
		zfree(o);
		zfree(v);
		zfree(p);
		zfree(t);
		break;
	neof:
		warnx("dcent_print: neof");
		zfree(o);
		zfree(v);
		zfree(p);
		zfree(t);
		goto eof;
	case DCSTR:
		if (fwrite(e->u.s.str, e->u.s.len, 1, fp) != 1 ||
		    fputc('\n', fp) == EOF)
			goto eof;
		break;
	default:
		abort();
	}
	return (0);
eof:
	warnx("dcent_print: EOF");
	return (-1);
}

FILE *
dcret_pop(struct data *d)
{
	struct dcret *r;

	assert(d != NULL);
	if (d->data == NULL || d->nmemb == 0)
		return (NULL);
	r = (struct dcret *)d->data + --d->nmemb;
	if (r->data != NULL)
		free(r->data);
	return (r->prev);
}

int
dcret_push(struct data *d, FILE *prev, void *data)
{
	struct dcret *r;

	assert(d != NULL);
	if ((r = dallot(d, 4)) == NULL)
		return (-1);
	r->prev = prev;
	r->data = data;
	return (0);
}

int
dcnum(struct dc *d, FILE *fp)
{
	struct dcent e;
	z_t i;
	z_t t;
	int c;

	assert(d != NULL && fp != NULL);
	e.type = DCNUM;
	zinit(e.u.n.v);
	zsetu(e.u.n.v, 0);
	zinit(e.u.n.k);
	zsetu(e.u.n.k, 1);
	zinit(i);
	zsetu(i, d->i);
	zinit(t);
	while ((c = fgetc(fp)) != EOF) {
		if (c >= '0' && c <= '9')
			c -= '0';
		else if (c >= 'A' && c <= 'F')
			c -= 'A';
		else
			break;
		zsetu(t, c);
		zmul(e.u.n.v, e.u.n.v, i);
		zadd(e.u.n.v, e.u.n.v, t);
	}
	if (c == '.')
		while ((c = fgetc(fp)) != EOF) {
			if (c >= '0' && c <= '9')
				c -= '0';
			else if (c >= 'A' && c <= 'F')
				c -= 'A';
			else
				break;
			zsetu(t, c);
			zmul(e.u.n.v, e.u.n.v, i);
			zmul(e.u.n.k, e.u.n.k, i);
			zadd(e.u.n.v, e.u.n.v, t);
		}
	if (c != EOF)
		ungetc(c, fp);
	(void)dcent_push(&d->stack, &e, 1);
	zfree(i);
	zfree(t);
	return (0);
}

int
dcstr(struct dc *d, FILE *fp)
{
	struct dcent e;
	unsigned b = 1;
	int c;

	assert(d != NULL && fp != NULL);
	d->str.nmemb = 0;
	while ((c = fgetc(fp)) != EOF) {
		switch (c) {
		case '\n':
			if (d->flags & DCNL) {
				warnx("Unexpected new-line");
				return (-1);
			}
			break;
		case '[':
			b++;
			break;
		case ']':
			if (--b == 0) {
				e.type = DCSTR;
				e.u.s.str = memdup(d->str.data,
				    d->str.nmemb);
				e.u.s.len = d->str.nmemb;
				(void)dcent_push(&d->stack, &e, 1);
				return (0);
			}
			break;
		}
		*(char *)dallot(&d->str, 32) = c;
	}
	warn("Unexpected EOF");
	return (-1);
}

int
dcprint(struct dc *d, FILE *fp)
{
	struct dcent *e;

	assert(d != NULL && d->o <= 16 && fp != NULL);
	if (d->stack.nmemb == 0)
		return (-1);
	return (dcent_print((struct dcent *)d->stack.data +
	    d->stack.nmemb - 1, d->o, fp));
}

int
dcprintc(struct dc *d, FILE *fp)
{
	struct dcent e[1];
	uint64_t u;

	assert(d != NULL && fp != NULL);
	if (dcent_pop(&d->stack, 0, e, nitems(e)) != 0)
		return (-1);
	switch (e[0].type) {
	case DCNUM:
		zdiv(e[0].u.n.v, e[0].u.n.v, e[0].u.n.k);
		while (zcmpu(e[0].u.n.v, 0) == 1) {
			zgetu(&u, e[0].u.n.v, 8);
			zrsh(e[0].u.n.v, e[0].u.n.v, 8);
			if (fputc(u, fp) == EOF)
				goto eof;
		}
		break;
	case DCSTR:
		if (fwrite(e[0].u.s.str, e[0].u.s.len, 1, fp) != 1)
			goto eof;
		break;
	}
	warnx("dcprint: done");
	dcent_free(e);
	return (0);
eof:
	warnx("dcprint: EOF");
	dcent_free(e);
	return (-1);
}

int
dcprintst(struct dc *d, FILE *fp)
{
	struct dcent *e;

	assert(d != NULL && d->o <= 16 && fp != NULL);
	if (d->stack.nmemb == 0)
		return (-1);
	for (e = (struct dcent *)d->stack.data + d->stack.nmemb;
	    (void *)e > d->stack.data; )
		if (dcent_print(--e, d->o, fp) != 0)
			return (-1);
	return (0);
}

int
dccmp(struct dc *d, int *o)
{
	struct dcent e[2];
	int r;

	assert(d != NULL && o != NULL);
	if (dcent_pop(&d->stack, DCNUM, e, nitems(e)) != 0)
		return (-1);
	if (zcmp(e[0].u.n.k, e[1].u.n.k) != 0) {
		zmul(e[0].u.n.v, e[0].u.n.v, e[1].u.n.k);
		zmul(e[1].u.n.v, e[1].u.n.v, e[0].u.n.k);
	}
	*o = zcmp(e[0].u.n.v, e[1].u.n.v);
	dcent_free(&e[0]);
	dcent_free(&e[1]);
	return (0);
}

void
dcneg(struct dc *d)
{
	struct dcent e[1];

	assert(d != NULL);
	if (dcent_pop(&d->stack, DCNUM, e, nitems(e)) != 0)
		return;
	zneg(e[0].u.n.v, e[0].u.n.v);
	(void)dcent_push(&d->stack, e, 1);
}

void
dcsqrt(struct dc *d)
{
	struct dcent e[1];
	z_t n;
	z_t x;
	z_t k;
	int c = 0;

	if (dcent_pop(&d->stack, DCNUM, e, nitems(e)) != 0)
		return;
	switch (zsignum(e[0].u.n.v)) {
	case -1:
		return;
	case 0:
		zsetu(e[0].u.n.v, 1);
		zsetu(e[0].u.n.k, 1);
		goto ret;
	}
	if (zcmp(e[0].u.n.k, d->k) == -1) {
		zmul(e[0].u.n.v, e[0].u.n.v, d->k);
		zdiv(e[0].u.n.v, e[0].u.n.v, e[0].u.n.k);
		zset(e[0].u.n.k, d->k);
	}
	if (zcmpu(e[0].u.n.k, 1) != 1) {
		c = 1;
		zsetu(e[0].u.n.k, 10);
		zmul(e[0].u.n.v, e[0].u.n.v, e[0].u.n.k);
	}
	zinit(x);
	zinit(n);
	zrsh(n, e[0].u.n.v, 1);
	do {
		zset(x, n);
		zmul(n, e[0].u.n.v, e[0].u.n.k);
		zdiv(n, n, x);
		zadd(n, n, x);
		zrsh(n, n, 1);
	} while (zcmp(x, n) != 0);
	if (c) {
		zdiv(e[0].u.n.v, n, e[0].u.n.k);
		zsetu(e[0].u.n.k, 1);
	} else
		zset(e[0].u.n.v, n);
	zfree(x);
	zfree(n);
ret:
	(void)dcent_push(&d->stack, e, 1);
}

void
dcdup(struct dc *d)
{
	struct dcent e[2];
	struct dcent dup;

	assert(d != NULL);
	if (dcent_pop(&d->stack, 0, e, 1) != 0 ||
	    dcent_dup(e + 1, e) != 0)
		return;
	(void)dcent_push(&d->stack, e, 2);
}

void
dcclr(struct dc *d)
{
	struct dcent *e;
	size_t i;

	assert(d != NULL);
	e = d->stack.data;
	for (i = 0; i < d->stack.nmemb; i++)
		dcent_free(&e[i]);
	d->stack.nmemb = 0;
}

void
dcpopi(struct dc *d)
{
	struct dcent e[1];
	unsigned i;

	assert(d != NULL);
	if (dcent_pop(&d->stack, DCNUM, e, nitems(e)) != 0)
		return;
	zdiv(e[0].u.n.v, e[0].u.n.v, e[0].u.n.k);
	if (zsignum(e[0].u.n.v) == -1 || zcmpu(e[0].u.n.v, 16) == 1) {
		warnx("Bad radix for `i'");
		return;
	}
	for (i = 0; zcmpu(e[0].u.n.v, i) == -1; i++)
		/* do nothing */;
	assert(i < 16);
	d->i = i;
	dcent_free(&e[0]);
}

void
dcpushi(struct dc *d)
{
	struct dcent e;

	assert(d != NULL);
	e.type = DCNUM;
	zinit(e.u.n.v);
	zsetu(e.u.n.v, d->i);
	zinit(e.u.n.k);
	zsetu(e.u.n.k, 1);
	(void)dcent_push(&d->stack, &e, 1);
}

void
dcpopk(struct dc *d)
{
	struct dcent e[1];

	assert(d != NULL);
	if (dcent_pop(&d->stack, DCNUM, e, nitems(e)) != 0)
		return;
	zdiv(e[0].u.n.v, e[0].u.n.v, e[0].u.n.k);
	zsetu(d->k, 10);
	zpow(d->k, d->k, e[0].u.n.v);
	dcent_free(e);
}

void
dcpushk(struct dc *d)
{
	struct dcent e;

	assert(d != NULL);
	e.type = DCNUM;
	zinit(e.u.n.v);
	zset(e.u.n.v, d->k);
	zinit(e.u.n.k);
	zsetu(e.u.n.k, 0);
	(void)dcent_push(&d->stack, &e, 1);
}

void
dcpopo(struct dc *d)
{
	struct dcent e[1];
	unsigned i;

	assert(d != NULL);
	if (dcent_pop(&d->stack, DCNUM, e, nitems(e)) != 0)
		return;
	zdiv(e[0].u.n.v, e[0].u.n.v, e[0].u.n.k);
	if (zsignum(e[0].u.n.v) == -1 || zcmpu(e[0].u.n.v, 16) == 1) {
		warnx("Bad radix for `o'");
		return;
	}
	for (i = 0; zcmpu(e[0].u.n.v, i) == -1; i++)
		/* do nothing */;
	assert(i < 16);
	d->o = i;
	dcent_free(&e[0]);
}

void
dcpusho(struct dc *d)
{
	struct dcent e;

	assert(d != NULL);
	e.type = DCNUM;
	zinit(e.u.n.v);
	zsetu(e.u.n.v, d->o);
	zinit(e.u.n.k);
	zsetu(e.u.n.k, 1);
	(void)dcent_push(&d->stack, &e, 1);
}

void
dcpushl(struct dc *d)
{
	struct dcent e[1];

	assert(d != NULL);
	if (dcent_pop(&d->stack, DCNUM, e, nitems(e)) != 0)
		return;
	zsetu(e[0].u.n.v, zstr_length(e[0].u.n.v, d->o));
	zsetu(e[0].u.n.k, 1);
	(void)dcent_push(&d->stack, e, 1);
}

void
dcpushx(struct dc *d)
{
	struct dcent e;

	assert(d != NULL);
	e.type = DCNUM;
	zinit(e.u.n.v);
	zsetu(e.u.n.v, zstr_length(d->k, 10) - 1);
	zinit(e.u.n.k);
	zsetu(e.u.n.k, 1);
	(void)dcent_push(&d->stack, &e, 1);
}

void
dcpushz(struct dc *d)
{
	struct dcent e;

	assert(d != NULL);
	e.type = DCNUM;
	zinit(e.u.n.v);
	zsetu(e.u.n.v, d->stack.nmemb);
	zinit(e.u.n.k);
	zsetu(e.u.n.k, 1);
	(void)dcent_push(&d->stack, &e, 1);
}


void
dcadd(struct dc *d)
{
	struct dcent e[2];

	assert(d != NULL);
	if (dcent_pop(&d->stack, DCNUM, e, nitems(e)) != 0)
		return;
	switch (zcmp(e[0].u.n.k, e[1].u.n.k)) {
	case -1:
		zmul(e[0].u.n.v, e[0].u.n.v, e[1].u.n.k);
		zmul(e[1].u.n.v, e[1].u.n.v, e[0].u.n.k);
		zadd(e[0].u.n.v, e[0].u.n.v, e[1].u.n.v);
		zdiv(e[0].u.n.v, e[0].u.n.v, e[0].u.n.k);
		zset(e[0].u.n.k, e[1].u.n.k);
		break;
	case 0:
		zadd(e[0].u.n.v, e[0].u.n.v, e[1].u.n.v);
		break;
	case 1:
		zmul(e[0].u.n.v, e[0].u.n.v, e[1].u.n.k);
		zmul(e[1].u.n.v, e[1].u.n.v, e[0].u.n.k);
		zadd(e[0].u.n.v, e[0].u.n.v, e[1].u.n.v);
		zdiv(e[0].u.n.v, e[0].u.n.v, e[1].u.n.k);
		break;
	}
	(void)dcent_push(&d->stack, e, 1);
	dcent_free(&e[1]);
}

void
dcsub(struct dc *d)
{
	struct dcent e[2];

	assert(d != NULL);
	if (dcent_pop(&d->stack, DCNUM, e, nitems(e)) != 0)
		return;
	switch (zcmp(e[0].u.n.k, e[1].u.n.k)) {
	case -1:
		zmul(e[0].u.n.v, e[0].u.n.v, e[1].u.n.k);
		zmul(e[1].u.n.v, e[1].u.n.v, e[0].u.n.k);
		zsub(e[1].u.n.v, e[1].u.n.v, e[0].u.n.v);
		zdiv(e[1].u.n.v, e[1].u.n.v, e[0].u.n.k);
		break;
	case 0:
		zsub(e[1].u.n.v, e[1].u.n.v, e[0].u.n.v);
		break;
	case 1:
		zmul(e[0].u.n.v, e[0].u.n.v, e[1].u.n.k);
		zmul(e[1].u.n.v, e[1].u.n.v, e[0].u.n.k);
		zsub(e[1].u.n.v, e[1].u.n.v, e[0].u.n.v);
		zdiv(e[1].u.n.v, e[1].u.n.v, e[1].u.n.k);
		zset(e[1].u.n.k, e[0].u.n.k);
		break;
	}
	(void)dcent_push(&d->stack, e + 1, 1);
	dcent_free(&e[0]);
}

void
dcmul(struct dc *d)
{
	struct dcent e[2];

	assert(d != NULL);
	if (dcent_pop(&d->stack, DCNUM, e, nitems(e)) != 0)
		return;
	zmul(e[0].u.n.v, e[0].u.n.v, e[1].u.n.v);
	switch (zcmp(e[0].u.n.k, e[1].u.n.k)) {
	case -1:
		zdiv(e[0].u.n.v, e[0].u.n.v, e[0].u.n.k);
		zset(e[0].u.n.k, e[1].u.n.k);
		break;
	case 0:
		break;
	case 1:
		zdiv(e[0].u.n.v, e[0].u.n.v, e[1].u.n.k);
		break;
	}
	(void)dcent_push(&d->stack, e, 1);
	dcent_free(&e[1]);
}

void
dcdiv(struct dc *d)
{
	struct dcent e[2];

	assert(d != NULL);
	if (dcent_pop(&d->stack, DCNUM, e, nitems(e)) != 0)
		return;
	if (zcmp(e[0].u.n.k, e[1].u.n.k) != 0) {
		zmul(e[0].u.n.v, e[0].u.n.v, e[1].u.n.k);
		zmul(e[1].u.n.v, e[1].u.n.v, e[0].u.n.k);
	}
	zmul(e[1].u.n.v, e[1].u.n.v, d->k);
	zdiv(e[1].u.n.v, e[1].u.n.v, e[0].u.n.v);
	zset(e[1].u.n.k, d->k);
	(void)dcent_push(&d->stack, e + 1, 1);
	dcent_free(&e[0]);
}

void
dcmod(struct dc *d)
{
	struct dcent e[2];

	assert(d != NULL);
	if (dcent_pop(&d->stack, DCNUM, e, nitems(e)) != 0)
		return;
	zmod(e[1].u.n.v, e[1].u.n.v, e[0].u.n.v);
	(void)dcent_push(&d->stack, e + 1, 1);
	dcent_free(&e[0]);
}

void
dcpow(struct dc *d)
{
	struct dcent e[2];
	int neg = 0;

	assert(d != NULL);
	if (dcent_pop(&d->stack, DCNUM, e, nitems(e)) != 0)
		return;
	zdiv(e[0].u.n.v, e[0].u.n.v, e[0].u.n.k);
	if (zsignum(e[0].u.n.v) == 0) {
		zsetu(e[0].u.n.v, 1);
		zsetu(e[0].u.n.k, 1);
	} else {
		if (zsignum(e[0].u.n.v) == -1) {
			zabs(e[0].u.n.v, e[0].u.n.v);
			neg = 1;
		}
		zpow(e[0].u.n.k, e[1].u.n.k, e[0].u.n.v);
		zpow(e[0].u.n.v, e[1].u.n.v, e[0].u.n.v);
		zdiv(e[0].u.n.k, e[0].u.n.k, e[1].u.n.k);
		zdiv(e[0].u.n.v, e[0].u.n.v, e[0].u.n.k);
		if (neg) {
			zmul(e[1].u.n.v, e[1].u.n.k, d->k);
			zdiv(e[0].u.n.v, e[1].u.n.v, e[0].u.n.v);
			zset(e[0].u.n.k, d->k);
		} else
			zset(e[0].u.n.k, e[1].u.n.k);
	}
	(void)dcent_push(&d->stack, e, 1);
	dcent_free(&e[1]);
}
